using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Text;
using Common.Utilities;


namespace PdfLib
{
    public class PdfTableDataFromTextTokens
    {
        #region settings
        private List<PdfTextLine> _Lines;
        private List<PdfTextToken> _TextTokens;
        private DataTable _TableContent;
        private const double _MinRelativeLineSpacing = 0.5;
        #endregion

        public PdfTableDataFromTextTokens(List<PdfTextToken> txtTokens, List<RectangleF> allRects, SizeF pageSize, List<string> colHeaders)
        {
            this._TextTokens = txtTokens;
            this._Lines = PdfTextReader.CreateLines(this._TextTokens);
            this._Lines = PdfTextReader.MergeLines(this._Lines, _MinRelativeLineSpacing);
            Dictionary<string, RectangleF> fieldRects = this.GetFieldRectangles(allRects, pageSize, colHeaders);
            this._TableContent = this.ReadTableContent(this._Lines, colHeaders, this._TextTokens, fieldRects);
        }

        public DataTable TableContent
        {
            get
            {
                return this._TableContent;
            }
        }

        private RectangleF DetectTableHeaderRectangle(List<PdfTextLine> lines, List<string> colHeaders)
        {
            RectangleF headerRect = new RectangleF();
            StringBuilder concatedLineText = new StringBuilder();
            lines.Sort();
            string headerFromFirstWordConcated = string.Empty;
            for (int i = 0; i < colHeaders.Count; i++)
            {
                string colHeader = colHeaders[i];
                string[] wordsInHeader = colHeader.Split(new char[] { ' ' });
                headerFromFirstWordConcated += wordsInHeader[0];
                if (i < colHeaders.Count - 1)
                    headerFromFirstWordConcated += " ";
            }
            int bestScore = 0;
            for (int i = 0; i < lines.Count; i++)
            {
                concatedLineText.Append(lines[i].LineContent + "\n");
                int distance =
                    StringUtil.LongestCommonSubString(lines[i].LineContent, headerFromFirstWordConcated);
                if (distance > bestScore)
                {
                    bestScore = distance;
                    headerRect = RectangleF.Union(headerRect, lines[i].Rect);
                }
            }
            return headerRect;
        }

        private DataTable ReadTableContent(List<PdfTextLine> lines, List<string> colHeaders, List<PdfTextToken> allTextTokens, Dictionary<string, RectangleF> tableFieldRects)
        {
            DataTable dtContent = new DataTable();
            foreach (string colName in tableFieldRects.Keys)
            {
                dtContent.Columns.Add(colName, typeof(string));
            }

            lines.Sort();
            RectangleF headerRect = this.DetectTableHeaderRectangle(lines, colHeaders);
            for (int i = 0; i < lines.Count; i++)
            {
                PdfTextLine line = lines[i];
                if (line.Rect.Y > headerRect.Y + headerRect.Height)
                {
                    DataRow drContent = dtContent.NewRow();
                    foreach (PdfTextToken txtToken in allTextTokens)
                    {
                        if (txtToken.TextRect.IntersectsWith(line.Rect))
                        {
                            string colName = this.GetCorrespondingColumn(txtToken.TextRect, tableFieldRects);
                            if (!string.IsNullOrEmpty(colName))
                            {
                                if (drContent[colName] != null)
                                {
                                    string fieldValue = drContent[colName].ToString();
                                    fieldValue += " " + txtToken.Text;
                                    drContent[colName] = fieldValue;
                                }
                                else
                                    drContent[colName] = txtToken.Text;
                            }
                        }
                    }
                    dtContent.Rows.Add(drContent);
                }
            }
            return dtContent;
        }

        private string GetCorrespondingColumn(RectangleF txtRect, Dictionary<string, RectangleF> fieldRects)
        {
            foreach (string colName in fieldRects.Keys)
            {
                RectangleF fieldRect = fieldRects[colName];
                if (fieldRect.X < txtRect.X && fieldRect.X + fieldRect.Width > txtRect.X + txtRect.Width)
                {
                    return colName;
                }
            }
            return string.Empty;
        }

        public Dictionary<string, RectangleF> GetFieldRectangles(List<RectangleF> allRects, SizeF pageSize, List<string> colHeaders)
        {



            Dictionary<string, RectangleF> fieldRects = new Dictionary<string, RectangleF>();
            List<SortableRectangle> sortableRects = new List<SortableRectangle>();
            foreach (RectangleF rect in allRects)
            {
                if (!this.HasBottomCells(rect, allRects, pageSize))
                {
                    SortableRectangle sortableRect = new SortableRectangle(rect);
                    sortableRects.Add(sortableRect);
                }
            }
            sortableRects.Sort();

            List<RectangleF> candiateRects = new List<RectangleF>();
            foreach (SortableRectangle sortableRect in sortableRects)
            {
                candiateRects.Add(sortableRect.Rect);
            }
            List<RectangleF> skipedRects = this.FindMissingRectangle(candiateRects, pageSize);
            if (skipedRects != null && skipedRects.Count > 0)
            {
                candiateRects.AddRange(skipedRects);
                sortableRects = new List<SortableRectangle>();
                foreach (RectangleF candidateRect in candiateRects)
                {
                    sortableRects.Add(new SortableRectangle(candidateRect));
                }
                sortableRects.Sort();
                candiateRects = new List<RectangleF>();
                foreach (SortableRectangle sortableRect in sortableRects)
                {
                    candiateRects.Add(sortableRect.Rect);
                }
            }


            //int startRectIndex = allRects.Count - colHeaders.Count;
            //if(startRectIndex<0)
            //{
            //    throw new Exception("Invalid table format");
            //}
            if (colHeaders.Count > sortableRects.Count)
            {
                for (int i = 0; i < sortableRects.Count; i++)
                {
                    fieldRects.Add(colHeaders[i], sortableRects[i].Rect);
                }
            }
            else
            {
                int startIndex = sortableRects.Count - colHeaders.Count;
                for (int i = startIndex; i < sortableRects.Count; i++)
                {
                    fieldRects.Add(colHeaders[i - startIndex], sortableRects[i].Rect);
                }
            }

            //Dictionary<string,float> fieldNameXPosition=new Dictionary<string, float>();
            Dictionary<float, RectangleF> xSortedRects = new Dictionary<float, RectangleF>();
            foreach (string fieldName in fieldRects.Keys)
            {
                RectangleF fieldRectangle = fieldRects[fieldName];
                // fieldNameXPosition.Add(fieldName, fieldRectangle.X);
                xSortedRects.Add(fieldRectangle.X, fieldRectangle);
            }
            List<float> xPositions = new List<float>();
            foreach (float x in xSortedRects.Keys)
            {
                xPositions.Add(x);
            }
            xPositions.Sort();
            string[] sortedFieldNames = new string[fieldRects.Count];
            fieldRects.Keys.CopyTo(sortedFieldNames, 0);
            for (int i = 0; i < sortedFieldNames.Length; i++)
            {
                float x = xPositions[i];
                RectangleF fieldRectangle = xSortedRects[x];
                fieldRects[sortedFieldNames[i]] = fieldRectangle;
            }
            fieldRects = this.AdjustRectangleAssignmentBasedOnColumnHeaderTokenPosition(fieldRects, candiateRects, this._TextTokens);

            return fieldRects;
        }

        private bool HasBottomCells(RectangleF currentRect, List<RectangleF> allRects, SizeF pageSize)
        {
            foreach (RectangleF otherRect in allRects)
            {
                if (otherRect.Width < pageSize.Width && otherRect.Height < pageSize.Height)
                {
                    if ((currentRect.X < otherRect.X + 4 && currentRect.X + currentRect.Width + 4 > otherRect.X) ||
                        (currentRect.X + 4 > otherRect.X && currentRect.X < otherRect.X + otherRect.Width + 4))
                    {
                        if (otherRect.Y + 4 >= currentRect.Y + currentRect.Height)
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        private Dictionary<string, RectangleF> AdjustRectangleAssignmentBasedOnColumnHeaderTokenPosition(Dictionary<string, RectangleF> fieldRects, List<RectangleF> candidateRects, List<PdfTextToken> allTextTokens)
        {
            Dictionary<string, RectangleF> adjustedFieldRects = new Dictionary<string, RectangleF>();
            allTextTokens.Sort();
            foreach (string colName in fieldRects.Keys)
            {
                string[] colWords = colName.Split(new char[] { ' ' });
                string colHeaderFirstWord = colWords[0];
                RectangleF adjustedRect = fieldRects[colName];
                for (int i = 0; i < allTextTokens.Count; i++)
                {
                    if (allTextTokens[i].Text.ToLower() == colHeaderFirstWord.ToLower())
                    {
                        RectangleF rectThatContainsText =
                            this.GetRectangleThatIncludeTextToken(candidateRects, allTextTokens[i]);
                        if (!rectThatContainsText.IsEmpty)
                        {
                            adjustedRect = rectThatContainsText;
                        }
                    }

                    if (allTextTokens[i].TextRect.Y + allTextTokens[i].TextRect.Height > adjustedRect.Y + adjustedRect.Height)
                    {
                        break;
                    }
                }
                adjustedFieldRects.Add(colName, adjustedRect);
            }

            return adjustedFieldRects;
        }

        private RectangleF GetRectangleThatIncludeTextToken(List<RectangleF> allRects, PdfTextToken headerToken)
        {
            foreach (RectangleF rect in allRects)
            {
                if (rect.X < headerToken.TextRect.X + 4 && rect.X + rect.Width + 4 > headerToken.TextRect.X + headerToken.TextRect.Width)
                {
                    return rect;
                }
            }
            return RectangleF.Empty;
        }

        private List<RectangleF> FindMissingRectangle(List<RectangleF> allRects, SizeF pageSize)
        {
            float yMin = pageSize.Height;
            float yMax = 0;
            foreach (RectangleF rect in allRects)
            {
                if (rect.Y < yMin)
                {
                    yMin = rect.Y;
                }
                if (rect.Y + rect.Height > yMax)
                {
                    yMax = rect.Y + rect.Height;
                }
            }

            List<Range<float>> rangesNotOccupied = new List<Range<float>>();
            Range<float> original = new Range<float>();
            original.FromValue = 0f;
            original.ToValue = pageSize.Width;
            rangesNotOccupied.Add(original);

            foreach (RectangleF rect in allRects)
            {
                Range<float> currentRange = new Range<float>();
                currentRange.FromValue = rect.X;
                currentRange.ToValue = rect.X + rect.Width;
                List<Range<float>> rangesCopy = new List<Range<float>>();
                for (int i = 0; i < rangesNotOccupied.Count; i++)
                {
                    Range<float> existingRange = rangesNotOccupied[i];
                    if (existingRange.OverlapWith(currentRange))
                    {
                        if (existingRange.FromValue <= currentRange.FromValue && existingRange.ToValue >= currentRange.ToValue)
                        {
                            Range<float> leftRange = new Range<float>();
                            leftRange.FromValue = existingRange.FromValue;
                            leftRange.ToValue = currentRange.FromValue;
                            Range<float> rightRange = new Range<float>();
                            rightRange.FromValue = currentRange.ToValue;
                            rightRange.ToValue = existingRange.ToValue;
                            if (leftRange.ToValue - leftRange.FromValue > 20)
                            {
                                rangesCopy.Add(leftRange);
                            }
                            if (rightRange.ToValue - rightRange.FromValue > 20)
                            {
                                rangesCopy.Add(rightRange);
                            }
                        }
                        else
                        {
                            if (existingRange.FromValue < currentRange.FromValue)
                            {
                                Range<float> leftOver = new Range<float>();
                                leftOver.FromValue = existingRange.FromValue;
                                leftOver.ToValue = currentRange.FromValue;
                                if (leftOver.ToValue - leftOver.FromValue > 20)
                                {
                                    rangesCopy.Add(leftOver);
                                }
                            }
                            else
                            {
                                Range<float> leftOver = new Range<float>();
                                leftOver.FromValue = currentRange.ToValue;
                                leftOver.ToValue = existingRange.ToValue;
                                if (leftOver.ToValue - leftOver.FromValue > 20)
                                {
                                    rangesCopy.Add(leftOver);
                                }
                            }
                        }
                    }
                    else
                    {
                        rangesCopy.Add(existingRange);
                    }
                }
                rangesNotOccupied = rangesCopy;
            }

            List<RectangleF> missingRects = new List<RectangleF>();
            if (rangesNotOccupied.Count > 0)
            {
                foreach (Range<float> range in rangesNotOccupied)
                {
                    RectangleF missingRect =
                        new RectangleF(range.FromValue, yMin, range.ToValue - range.FromValue, yMax - yMin);
                    missingRects.Add(missingRect);
                }
            }
            return missingRects;
        }


    }
}
